///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file coeff_obj.h
///
/// \brief   header file for the coefficient object class
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef PCPS_COEFF_OBJ_HEADER
#define PCPS_COEFF_OBJ_HEADER

#include<src/pcps.h>
#include<src/input.h>
#include<src/mantissa_exponent.h>
#include<src/wfn_data.h>
#include<src/configuration.h>

namespace pcps {

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief  a class used to create simplifying typedefs for shared pointers
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  template<class T> class PtrTypes {
    public:
      typedef boost::shared_ptr<T> p;
      typedef std::vector<boost::shared_ptr<T> > vp;
      typedef boost::shared_ptr<std::vector<boost::shared_ptr<T> > > pvp;
  };

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief   Base class for an object representing a coefficient for a given occupation number
  ///          vector.
  ///
  /// The object should be capable of calculating the coefficient for a given configuration,
  /// calculating the coefficient's derivative with respect to the wavefunction's variable data,
  /// and (hopefully) efficiently recomputing the coefficient for a configuration similar to the
  /// previous configuration (e.g. for a Monte Carlo move).
  ///
  /// The coefficient may depend on integer and floating point wavefunction data as well as other
  /// coefficient objects, allowing for the overall wavefunction coefficient to be a non-trivial
  /// combination of many different types of sub-coefficients (such as a Jastrow factor coefficient
  /// multiplied by a Slater determinant coefficient).
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  template<class S> class CoeffObj {

    protected:

      /// \brief  the name of the coefficient
      std::string _name;

      /// \brief  the value of the coefficient for the currently initialized configuration
      pcps::MantExp<S> _curr_val;

      /// \brief  pointers to floating point wavefunction data on which the coefficient depends
      typename pcps::PtrTypes<pcps::WfnData<S> >::pvp _wf_deps;

      /// \brief  pointers to integer wavefunction data on which the coefficient depends
      typename pcps::PtrTypes<pcps::WfnData<int> >::pvp _wi_deps;

      /// \brief  pointers to other coefficient objects on which the coefficient depends
      typename pcps::PtrTypes<pcps::CoeffObj<S> >::pvp _co_deps;

    public:

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief constructor that initializes the pointers to point vectors of the specified size
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      CoeffObj(const int nwf = 0, const int nwi = 0, const int nco = 0)
        : _wf_deps( new typename pcps::PtrTypes<pcps::WfnData<S>   >::vp(nwf) ),
          _wi_deps( new typename pcps::PtrTypes<pcps::WfnData<int> >::vp(nwi) ),
          _co_deps( new typename pcps::PtrTypes<pcps::CoeffObj<S>  >::vp(nco) )
      {}

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief destructor
      ///
      /// Declared virtual so derived classes' destructors are called when deleting parent class's pointer.
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual ~CoeffObj() {}

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief returns the name of the component
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      const std::string & name() const { return this->_name; }

      // ///////////////////////////////////////////////////////////////////////////////////////////////////
      // /// \brief returns the value of the coefficient for the currently initialized configuration
      // ///        with noise removed
      // ///
      // /// \param[in]      config   the current lattice and noise configuration
      // ///
      // ///////////////////////////////////////////////////////////////////////////////////////////////////
      // virtual pcps::MantExp<S> noiseless_value(const pcps::Configuration<S> & config) { return this->_curr_val; }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief returns the value of the coefficient for the currently initialized configuration
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual pcps::MantExp<S> get_value() { return this->_curr_val; }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief returns the value of the coefficient for an arbitrary configuration
      ///
      /// \param[in]      config   the configuration whose coefficient we want
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual pcps::MantExp<S> get_value(const pcps::Configuration<S> & config) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief returns the value of the coefficient for a configuration that differs from the
      ///        currently initialized configuration as specified.
      ///
      /// \param[in]      new_config  the lattice configuration whose coefficient we want
      /// \param[in]      old_config  the lattice configuration the object is currently initialized for
      /// \param[in]      diff        an object specifying how the configurations differ
      ///
      /// \return the coefficient at the new configuration
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual pcps::MantExp<S> get_value(const pcps::Configuration<S> & new_config,
                                         const pcps::Configuration<S> & old_config,
                                         const pcps::ConfigDiff<S> & diff) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Computes the ratios of the noiseless coefficients at each singly excited configuration
      ///        to the noisy coefficient at the current configuration
      ///
      /// \param[in]      config      the lattice configuration the object is currently initialized for
      /// \param[out]     a_ratios    on exit, the matrix of excitation ratios for alpha excitations
      /// \param[out]     b_ratios    on exit, the matrix of excitation ratios for beta excitations
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void single_excitation_ratios(const pcps::Configuration<S> & config,
                                            std::vector<S> & a_ratios,
                                            std::vector<S> & b_ratios) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Adds the derivatives of the coefficient with respect to the wavefunction's data
      ///        variables to the supplied vector after multiplying the derivatives by a constant.
      ///        The derivatives are computed assuming that the object is currently initialized for the
      ///        supplied lattice configuration.
      ///
      /// \param[in]      config  the lattice configuration the object is currently initialized for
      /// \param[in]      alpha   constant to multiply the coefficient's derivatives by
      /// \param[out]     der_vec vector to which to add the derivative contributions from this object
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void add_derivatives(const pcps::Configuration<S> & config,
                                   const pcps::MantExp<S> & alpha,
                                   std::vector<S> & der_vec) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief For each orbital pair excitation, contracts the wavefunction's derivatives at the
      ///        corresponding excited configuration with the supplied trial vector.
      ///        It is assumed that the object is initialized for the supplied configuration.
      ///
      /// \param[in]      config      the lattice configuration to excite from.
      /// \param[in]      alpha       constant to multiply the contraction by
      /// \param[in]      trial_vec   the vector to be contracted
      /// \param[in,out]  out_vec_a   vector to which to add the alpha orbital result
      /// \param[in,out]  out_vec_b   vector to which to add the beta orbital result
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void contract_der_to_orb_pairs(const pcps::Configuration<S> & config,
                                             const pcps::MantExp<S> & alpha,
                                             const std::vector<S> & trial_vec,
                                             std::vector<S> & out_vec_a,
                                             std::vector<S> & out_vec_b) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief For each wavefunction variable, contracts the various singly excited configurations'
      ///        wavefunction derivatives with respect to that variable with the supplied trial vector.
      ///        It is assumed that the object is initialized for the supplied configuration.
      ///
      /// \param[in]      config        the lattice configuration to excite from.
      /// \param[in]      alpha         constant to multiply the contraction by
      /// \param[in]      trial_vec_a   the vector to be contracted for alpha orbitals
      /// \param[in]      trial_vec_b   the vector to be contracted for beta orbitals
      /// \param[in,out]  out_vec       vector to which to add the result of the contraction
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void contract_orb_pairs_to_der(const pcps::Configuration<S> & config,
                                             const pcps::MantExp<S> & alpha,
                                             const std::vector<S> & trial_vec_a,
                                             const std::vector<S> & trial_vec_b,
                                             std::vector<S> & out_vec) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Multiply the supplied trial vector by the wavefunction derivative outer product matrix
      ///        (the local contribution to the overlap matrix) for the given configuration.
      ///        It is assumed that the object is initialized for the supplied configuration.
      ///
      /// \param[in]      config      the lattice configuration to excite from.
      /// \param[in]      alpha       constant to multiply the contraction by
      /// \param[in]      trial_vec   the vector to be contracted
      /// \param[in,out]  out_vec     vector to which to add the result of the contraction
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void contract_der_to_der(const pcps::Configuration<S> & config,
                                       const pcps::MantExp<S> & alpha,
                                       const std::vector<S> & trial_vec,
                                       std::vector<S> & out_vec) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Initializes the object for the given lattice configuration.
      ///
      /// \param[in]      config  the lattice configuration we want the object initialized for
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void initialize(const pcps::Configuration<S> & config) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Updates the object so that it is initialized for a new configuration that differs from
      ///        the currently initialized configuration as specified.
      ///
      /// \param[in]      new_config  the lattice configuration we want to object to be initialized for
      /// \param[in]      old_config  the lattice configuration the object is currently initialized for
      /// \param[in]      diff        an object specifying how the configurations differ
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void update(const pcps::Configuration<S> & new_config,
                          const pcps::Configuration<S> & old_config,
                          const pcps::ConfigDiff<S> & diff) = 0;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief returns the number of floating point variables that the component depends upon
      ///
      /// \param[in,out]  counted_dependencies  a set containing the names of wavefunction floating point
      ///                                       data dependencies that have already been counted towards
      ///                                       the total number of variables
      ///
      /// \return the number of floating point variables that the component depends upon
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      int nfvar(std::set<std::string> & counted_dependencies = std::set<std::string>()) const {

        // zero the total
        int retval = 0;

        // for each of this object's wavefunction floating point data dependencies, add its number of
        // variables if they have not been counted already
        typename pcps::PtrTypes<pcps::WfnData<S> >::vp::const_iterator w;
        for (w = this->_wf_deps->begin(); w != this->_wf_deps->end(); w++)
          if ( counted_dependencies.count((**w).name()) == 0 ) {
            retval += (**w).size();
            counted_dependencies.insert((**w).name());
          }

        // add the variables from each coefficient object dependency
        typename pcps::PtrTypes<pcps::CoeffObj<S> >::vp::const_iterator c;
        for (c = this->_co_deps->begin(); c != this->_co_deps->end(); c++)
          retval += (**c).nfvar(counted_dependencies);

        // return the total
        return retval;

      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief converts the object into the command that will create the object
      ///
      /// \param[in,out]  arch     an archive in which to store the command
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      virtual void to_command(pcps::Archive & arch) const = 0;

  };

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  overload the << operator to enable easy archiving of coefficient objects
///
/// \param[in,out]  arch     an archive in which to store the coefficient object
/// \param[in,out]  c        the coefficient object we wish to store
///
/// \return the archive
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class T>
inline pcps::Archive & operator<<(pcps::Archive & arch, const pcps::CoeffObj<T> & c) { c.to_command(arch); return arch; }

#endif
